package com.dream.auth.security;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class RedisSecurityContextRepository implements SecurityContextRepository {

	private final RedisTemplate<String, Object> redisTemplate;

	private final JwtUtils jwtUtils;

	private static final String SECURITY_CONTEXT_PREFIX = "security:context:";

	private static final long CONTEXT_TIMEOUT = 24; // 24小时

	private final AntPathMatcher pathMatcher = new AntPathMatcher();

	private static final List<String> IGNORE_PATHS = Arrays.asList(
			// 静态资源
			"/css/**", "/js/**", "/images/**", "/fonts/**", "/favicon.ico",
			// Swagger/Knife4j
			"/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs/**", "/swagger-ui.html",
			"/swagger-ui/**", "/v3/api-docs/**",
			// 认证相关
			"/login", "/auth/login", "/auth/refresh-token",
			// OAuth2相关
			"/oauth2/**", "/authorize", "/token",
			// Actuator端点
			"/actuator/**",
			// 错误页面
			"/error");

	@SuppressWarnings("deprecation")
	@Override
	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		HttpServletRequest request = requestResponseHolder.getRequest();
		if (shouldIgnore(request)) {
			return SecurityContextHolder.createEmptyContext();
		}

		String token = extractToken(request);
		if (token != null) {
			try {
				String username = jwtUtils.extractUsername(token);
				SecurityContext context =
						(SecurityContext) redisTemplate.opsForValue().get(SECURITY_CONTEXT_PREFIX + username);
				if (context != null) {
					return context;
				}
			} catch (Exception e) {
				log.debug("Failed to load security context from redis", e);
			}
		}
		return SecurityContextHolder.createEmptyContext();
	}

	@Override
	public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
		if (shouldIgnore(request)) {
			return;
		}

		if (context != null && context.getAuthentication() != null) {
			String username = context.getAuthentication().getName();
			redisTemplate.opsForValue()
					.set(SECURITY_CONTEXT_PREFIX + username, context, CONTEXT_TIMEOUT, TimeUnit.HOURS);
		}
	}

	@Override
	public boolean containsContext(HttpServletRequest request) {
		if (shouldIgnore(request)) {
			return false;
		}

		String token = extractToken(request);
		if (token != null) {
			try {
				String username = jwtUtils.extractUsername(token);
				return Boolean.TRUE.equals(redisTemplate.hasKey(SECURITY_CONTEXT_PREFIX + username));
			} catch (Exception e) {
				log.debug("Failed to check security context in redis", e);
			}
		}
		return false;
	}

	private String extractToken(HttpServletRequest request) {
		String bearerToken = request.getHeader("Authorization");
		if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
			return bearerToken.substring(7);
		}
		return null;
	}

	private boolean shouldIgnore(HttpServletRequest request) {
		String requestPath = request.getRequestURI();
		return IGNORE_PATHS.stream().anyMatch(pattern -> pathMatcher.match(pattern, requestPath));
	}

}